package org.beetl.support;

import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.beetl.sql.core.SQLManager;

import jodd.bean.BeanUtil;

/**
 * GenericBeetlDao.
 * 
 * @param <T>
 *         the generic type
 * @author zhoupan.
 */
public class GenericBeetlDao<T> implements GenericDao<T> {

 /** SessionFactory. */

 protected SQLManager sqlManager;

 /** The template. */
 protected SqlManagerTemplate template;

 /** 实体类类型(由构造方法自动赋值). */
 protected Class<?> entityClass;

 /** The id generation type. */
 protected IDGenerationType idGenerationType = IDGenerationType.AUTO;

 public Class<?> getEntityClass() {
  return entityClass;
 }

 public void setEntityClass(Class<?> entityClass) {
  this.entityClass = entityClass;
 }

 public IDGenerationType getIdGenerationType() {
  return idGenerationType;
 }

 public void setIdGenerationType(IDGenerationType idGenerationType) {
  this.idGenerationType = idGenerationType;
 }

 /**
  * Instantiates a new generic mybatis dao.
  */
 public GenericBeetlDao() {
  super();
  entityClass = GenericUtils.getSuperClassGenericType(super.getClass());
  onSetting();
 }

 /**
  * On setting.
  */
 public void onSetting() {

 }

 public SQLManager getSqlManager() {
  return sqlManager;
 }

 public void setSqlManager(SQLManager sqlManager) {
  this.sqlManager = sqlManager;
 }

 /**
  * Gets the template.
  * 
  * @return the template
  */
 public SqlManagerTemplate getTemplate() {
  if (this.template == null) {
   this.template = new SqlManagerTemplate();
   this.template.setSqlManager(this.getSqlManager());
  }
  return template;
 }

 /** The namespace. */
 private String namespace = "";

 /**
  * Gets the namespace.
  * 
  * @return the namespace
  */
 public String getNamespace() {
  return namespace;
 }

 /**
  * Sets the namespace.
  * 
  * @param namespace
  *         the new namespace
  */
 public void setNamespace(String namespace) {
  this.namespace = namespace;
 }

 /**
  * Statement.
  * 
  * @param methodName
  *         the method name
  * @return the string
  */
 public String statement(String methodName) {
  if (CommonSupport.isBlank(this.namespace)) {
   this.namespace = entityClass.getName();
  }
  return this.namespace + "." + methodName;
 }

 public List<T> query(Map<String, Object> paramMap, int offset, int limit) {
  return this.query(paramMap, offset, limit, "query");
 }

 /**
  * Query.
  * 
  * @param paramMap
  *         the param map
  * @param offset
  *         the offset
  * @param limit
  *         the limit
  * @param methodName
  *         the method name
  * @return the list
  */
 public List<T> query(Map<String, Object> paramMap, int offset, int limit, String methodName) {
  return (List<T>) this.getTemplate().selectList(statement(methodName), this.entityClass, paramMap, offset, limit);
 }

 /**
  * Query.
  * 
  * @param paramMap
  *         the param map
  * @param methodName
  *         the method name
  * @return the list
  */
 public List<T> query(Map<String, Object> paramMap, String methodName) {
  return (List<T>) this.getTemplate().selectList(statement(methodName), this.entityClass, paramMap);
 }

 /** The name mapper. */
 NameMapper nameMapper = new DefaultNameMapper();

 /**
  * Gets the name mapper.
  * 
  * @return the name mapper
  */
 public NameMapper getNameMapper() {
  return nameMapper;
 }

 /**
  * Sets the name mapper.
  * 
  * @param nameMapper
  *         the new name mapper
  */
 public void setNameMapper(NameMapper nameMapper) {
  this.nameMapper = nameMapper;
 }

 /**
  * Gets the table alias.
  * 
  * @return the table alias
  */
 public String getTableAlias() {
  return CommonSupport.uncapitalize(this.entityClass.getSimpleName());
 }

 public List<T> query(Map<String, Object> paramMap, String orderBy, int offset, int limit) {
  return query(paramMap, orderBy, offset, limit, "query");
 }

 public List<T> query(Map<String, Object> paramMap, String orderBy, int offset, int limit, String methodName) {
  return this.query(new QueryFilterBuilder().params(paramMap).orderBy(orderBy).offset(offset).limit(limit).build(), methodName);
 }

 public List<T> query(QueryFilter filter) {
  return query(filter, "query");
 }

 /**
  * Query.
  * 
  * @param filter
  *         the filter
  * @param methodName
  *         the method name
  * @return the list
  */
 public List<T> query(QueryFilter filter, String methodName) {
  Map<String, Object> params = new HashMap<String, Object>();
  if (filter.getParams() != null) {
   params.putAll(filter.getParams());
  }
  if (CommonSupport.isNotBlank(filter.getOrderBy())) {
   params.put("orderBy", OrderByField.buildOrderBySql(this.getNameMapper(), filter.getOrderBy(), this.getTableAlias()));
  }
  int offset = filter.hasOffset() ? filter.getOffset() : 0;
  int limit = filter.hasLimit() ? filter.getLimit() : Integer.MAX_VALUE;
  LogSupport.info(" offset:{} limit:{}", new Object[] { offset, limit });
  return (List<T>) this.getTemplate().selectList(statement(methodName), this.entityClass, params, offset, limit);
 }

 public int insert(T entity) {
  return insert(entity, "insert");
 }

 public int insert(T entity, String methodName) {
  return this.getTemplate().insert(statement(methodName), entity);
 }

 public int update(T entity) {
  return update(entity, "update");
 }

 public int update(T entity, String methodName) {
  return this.getTemplate().update(statement(methodName), entity);
 }

 public int delete(T entity) {
  return delete(entity, "delete");
 }

 public int delete(T entity, String methodName) {
  return this.getTemplate().delete(statement(methodName), entity);
 }

 public int deleteAll(Map<String, Object> paramMap) {
  return this.deleteAll(paramMap, "deleteAll");
 }

 public int deleteAll(Map<String, Object> paramMap, String methodName) {
  return this.getTemplate().delete(statement(methodName), paramMap);
 }

 public long count(Map<String, Object> paramMap) {
  return count(paramMap, "count");
 }

 /**
  * Count.
  * 
  * @param paramMap
  *         the param map
  * @param methodName
  *         the method name
  * @return the long
  */
 public long count(Map<String, Object> paramMap, String methodName) {
  return this.getTemplate().selectOne(statement(methodName), Long.class, paramMap);
 }

 /** The primary key fields. */
 private String[] primaryKeyFields = new String[] {};

 /**
  * Gets the primary key fields.
  * 
  * @return the primary key fields
  */
 public String[] getPrimaryKeyFields() {
  return primaryKeyFields;
 }

 /**
  * Sets the primary key fields.
  * 
  * @param primaryKeyFields
  *         the new primary key fields
  */
 public void setPrimaryKeyFields(String[] primaryKeyFields) {
  this.primaryKeyFields = primaryKeyFields;
 }

 /**
  * Sets the primary key fields.
  * 
  * @param primaryKeyFields
  *         the new primary key fields
  */
 public void setPrimaryKeyFields(String primaryKeyFields) {
  setPrimaryKeyFields(CommonSupport.split(primaryKeyFields, ","));
 }

 /**
  * Gets the primary key.
  * 
  * @param o
  *         the o
  * @return the primary key
  */
 public Map<String, Object> getPrimaryKey(T o) {
  String[] keyFields = this.getPrimaryKeyFields();
  Map<String, Object> keyValues = new HashMap<String, Object>();
  if (keyFields == null || keyFields.length == 0) {
   throw new RuntimeException("必须指定主键字段名称.");
  }
  for (int i = 0; i < keyFields.length; i++) {
   try {
    String keyField = keyFields[i];
    Object keyFieldValue = BeanUtil.getProperty(o, keyField);
    if (keyFieldValue != null) {
     keyValues.put(keyField, BeanUtil.getProperty(o, keyField));
    }
   } catch (Throwable e) {
    throw new RuntimeException(e);
   }
  }
  return keyValues;
 }

 public T get(Serializable id) {
  Map<String, Object> keyValues = getPrimaryKey(id);
  // 根据主键查询.
  List<T> items = this.query(keyValues, 0, 1);
  if (items.size() > 0) {
   return items.get(0);
  } else {
   return null;
  }
 }

 /**
  * Gets the primary key.
  * 
  * @param id
  *         the id
  * @return the primary key
  */
 public Map<String, Object> getPrimaryKey(Serializable id) {
  String[] keyFields = this.getPrimaryKeyFields();
  Map<String, Object> keyValues = new HashMap<String, Object>();
  if (keyFields == null || keyFields.length == 0) {
   throw new RuntimeException("必须指定主键字段名称.");
  }
  // 单一字段主键
  if (keyFields.length == 1) {
   keyValues.put(keyFields[0], id);
  } else {
   // 联合主键
   String[] values = CommonSupport.split(id.toString(), "_");
   if (values == null || values.length == 0) {
    throw new RuntimeException("必须指定主键字段值.");
   }
   if (values.length < keyFields.length) {
    throw new RuntimeException("主键字段值数量必须为" + keyFields.length);
   }
   for (int i = 0; i < keyFields.length; i++) {
    keyValues.put(keyFields[i], values[i]);
   }
  }
  return keyValues;
 }

 /**
  * doGenerateKey
  * 
  * @param entity
  *         the entity
  * @param keyField
  *         the key field
  * @return the string
  */
 public Object doGenerateKey(T entity, String keyField) {
  @SuppressWarnings("rawtypes")
  Class keyFieldType = BeanUtil.getPropertyType(entity, keyField);
  LogSupport.debug("doGenerateKey keyField:{} keyFieldType:{}", keyField, keyFieldType);
  if (idGenerationType.isAuto()) {
   if (keyFieldType.equals(String.class)) {
    return onGenerateUUID();
   }
   if (Number.class.isAssignableFrom(keyFieldType)) {
    return this.onGetNextValueFromSequence();
   }
  } else if (idGenerationType.isUuid()) {
   return this.onGenerateUUID();
  } else if (idGenerationType.isSeq()) {
   return this.onGetNextValueFromSequence();
  }
  throw new RuntimeException("IDGenerationType：" + this.idGenerationType + " for keyFieldType " + keyFieldType.getName() + " on field "
    + keyField + " is not supported or implemented.");
 }

 /**
  * On generate uuid.
  * 
  * @return the string
  */
 public String onGenerateUUID() {
  return java.util.UUID.randomUUID().toString().replaceAll("-", "").toUpperCase();
 }

 /** The id sequence name. */
 protected String idSequenceName;

 /**
  * On get next value from sequence.
  * 
  * @return the object
  */
 public Object onGetNextValueFromSequence() {
  String idSequenceNameUsed = this.idSequenceName;
  // 没指定序列名称,根据默认命名规则生成.
  if (CommonSupport.isBlank(idSequenceNameUsed)) {
   idSequenceNameUsed = this.getNameMapper().getColumnName(this.entityClass.getSimpleName()) + "_SEQ";
  }
  return getNextValueFromSequence(idSequenceNameUsed);
 }

 /**
  * Gets the next value from sequence.
  * 
  * @param seq
  *         the seq
  * @return the next value from sequence
  */
 public long getNextValueFromSequence(String seq) {
  // throw new RuntimeException("getNextValueFromSequence " + seq + " not
  // implemented!");
  Map<String, Object> params = new HashMap<String, Object>();
  params.put("seq", seq);
  return this.getTemplate().selectOne("support.getNextValueFromSequence", Long.class, params);
 }

 /**
  * 默认生成字符串类型的单一主键.
  * 
  * @param entity
  *         the entity
  */
 public void onGeneratePrimaryKey(T entity) {
  if (this.primaryKeyFields != null && this.primaryKeyFields.length == 1) {
   String keyField = this.primaryKeyFields[0];
   try {
    LogSupport.debug("onGeneratePrimaryKey:  entity:{} keyField:{}", entity, keyField);
    Object singleKeyValue = BeanUtil.getProperty(entity, keyField);
    LogSupport.debug("onGeneratePrimaryKey:  {}={}", keyField, singleKeyValue);
    if (singleKeyValue == null || CommonSupport.isBlank(singleKeyValue.toString())) {
     BeanUtil.setProperty(entity, keyField, this.doGenerateKey(entity, keyField));
    }
   } catch (Throwable e) {
    LogSupport.error("can't read primary key property {} from entity {},cause :{}", new Object[] { keyField, entity, e });
   }
  }
 }

 /*
  * (non-Javadoc)
  * 
  * @see com.huashang.support.GenericDao#save(java.lang.Object)
  */
 public int save(T entity) {
  // 生成主键.
  if (!this.idGenerationType.isIdentity()) {
   this.onGeneratePrimaryKey(entity);
  }
  Map<String, Object> primaryKey = this.getPrimaryKey(entity);
  // 没指定主键,依赖数据库生成.
  if (primaryKey == null || primaryKey.isEmpty()) {
   LogSupport.debug("没有指定主键字段值,执行insert操作.");
   return insert(entity);
  }
  long countByPrimaryKey = this.count(primaryKey);
  if (countByPrimaryKey > 1) {
   throw new RuntimeException(String.format("根据主键%s找到%s条记录.", new Object[] { primaryKey, countByPrimaryKey }));
  }
  if (countByPrimaryKey == 1) {
   return update(entity);
  } else {
   return insert(entity);
  }
 }

 /*
  * (non-Javadoc)
  * 
  * @see com.huashang.support.GenericDao#delete(java.io.Serializable)
  */
 public int delete(Serializable id) {
  Map<String, Object> primaryKey = this.getPrimaryKey(id);
  if (primaryKey == null || primaryKey.isEmpty()) {
   throw new RuntimeException("必须指定主键字段值.");
  }
  return deleteAll(primaryKey);
 }

}